home *** CD-ROM | disk | FTP | other *** search
- ;;; -*- Package: ASSEMBLER -*-
- ;;;
- ;;; **********************************************************************
- ;;; This code was written as part of the CMU Common Lisp project at
- ;;; Carnegie Mellon University, and has been placed in the public domain.
- ;;; If you want to use this code or any part of CMU Common Lisp, please contact
- ;;; Scott Fahlman or slisp-group@cs.cmu.edu.
- ;;;
- (ext:file-comment
- "$Header: assem-check.lisp,v 1.3 91/10/18 17:59:10 ram Exp $")
- ;;;
- ;;; **********************************************************************
- ;;;
- ;;; Stuff to verify the legality of register allocation by examining the
- ;;; assembly output. If the same register holds two things (live TNs) at the
- ;;; same time, we have a problem.
- ;;;
- (in-package "ASSEMBLER")
- (export '(segment-check-registers))
- (in-package "C")
- (import '(do-live-tns ir2-block-live-in ir2-block-block print-tn sb-kind tn
- vop-block vop-info vop-info-save-p vop-save-set tn-reads tn-kind
- tn-number tn-writes vop-refs vop vop-info-arg-costs
- vop-info-result-costs vop-info-move-args vop-results vop-args
- vop-temps vop-info-arg-types vop-info-name)
- "ASSEMBLER")
- (in-package "ASSEMBLER")
-
- ;;; The segment we are currently checking.
- ;;;
- (defvar *check-segment*)
-
- ;;; The exclusive end of the block we are currently checking.
- ;;;
- (defvar *check-end*)
-
- ;;; REGISTER-LOSSAGE-ERROR -- Internal
- ;;;
- ;;; Print out a hopefully-descriptive error message describing the context
- ;;; in which a register is twice-used. Old is the cons (TN . Instruction)
- ;;; describing the previously live value.
- ;;;
- (defun register-lossage-error (sb offset tn write-p old inst)
- (let ((tn-name (with-output-to-string (s)
- (print-tn tn s)))
- (old-name (with-output-to-string (s)
- (print-tn (car old) s)))
- (old-inst (cdr old)))
- (cerror "Ignore it."
- "Location ~D in ~A SB in use by both ~A and ~A:~%~A~&"
- offset (sb-name sb) tn-name old-name
- (with-output-to-string (s)
- (dump-segment
- *check-segment* :stream s
- :start inst :end (if old-inst (node-next old-inst) *check-end*)
- :markers `((,inst "*** ~A ~:[read~;written~] here:~%"
- ,tn-name ,write-p)
- (,(cdr old) "*** ~A read here:~%" ,old-name)))))))
-
-
- ;;; FIND-TARGETING-PATH -- Internal
- ;;;
- ;;; Return true if TN is targeted into Old-TN (possibly indirectly through
- ;;; multiple TNs.) We do a graph walk to find indirect targeting paths. Flags
- ;;; is has a T entry for every TN that we have already reached during the walk.
- ;;;
- (defun find-targeting-path (tn old-tn flags)
- (cond
- ((gethash tn flags) nil)
- (t
- (setf (gethash tn flags) t)
- (do ((ref (tn-reads tn) (tn-ref-next ref)))
- ((null ref) nil)
- (let ((target (tn-ref-target ref)))
- (when target
- (let ((ttn (tn-ref-tn target)))
- (when (or (eq ttn old-tn)
- (eq (tn-ref-load-tn target) old-tn)
- (find-targeting-path ttn old-tn flags))
- (return t)))))))))
-
- (defparameter ignored-optimizable-vops '(c:allocate-full-call-frame))
-
- ;;; CHECK-FOR-EXCEPTIONS -- Internal
- ;;;
- ;;; This is one place where a hueristic component enters. We ignore
- ;;; sequences where the first TN (TN) is targeted into the second TN (Old)
- ;;; along a read path. If TN is a load-tn, then we scan the refs for Inst's
- ;;; VOP to find the original TN.
- ;;;
- ;;; We also ignore any cases where Old is written by certain VOPs that can be
- ;;; entirely optimized away.
- ;;;
- (defun check-for-exceptions (tn old write-p inst)
- (declare (ignore write-p))
- (or (find-targeting-path
- (if (eq (tn-kind tn) :load)
- (do ((ref (vop-refs (node-vop inst))
- (tn-ref-next-ref ref)))
- ((eq (tn-ref-load-tn ref) tn) (tn-ref-tn ref)))
- tn)
- (car old)
- (make-hash-table :test #'eq))
- (do ((ref (tn-writes (car old)) (tn-ref-next ref)))
- ((null ref) nil)
- (when (member (vop-info-name (vop-info (tn-ref-vop ref)))
- ignored-optimizable-vops)
- (return t)))))
-
-
- ;;; NOTE-TN-REF -- Internal
- ;;;
- ;;; Notice a reference to TN by Inst. If there is a problem, signal an
- ;;; error. If the TN has no number, we guess that it is a random TN (not
- ;;; allocated by the allocator), so we ignore the reference.
- ;;;
- (defun note-tn-ref (tn write-p inst)
- (if (tn-number tn)
- (let* ((sc (tn-sc tn))
- (sb (sc-sb sc)))
- (when (eq (sb-kind sb) :finite)
- (let ((live (finite-sb-live-tns sb)))
- (loop for i from (tn-offset tn)
- repeat (sc-element-size sc) do
- (let ((old (svref live i)))
- (when (and old (not (eq (car old) tn))
- (not (check-for-exceptions tn old write-p inst)))
- (register-lossage-error sb i tn write-p old inst)))
- (setf (svref live i) (if write-p nil (cons tn inst)))))))
- (assert (and (eq (tn-kind tn) :normal)
- (not (or (tn-reads tn) (tn-writes tn))))))
-
- (undefined-value))
-
-
- ;;; CLEAR-LIVE-SET -- Internal
- ;;;
- ;;; Mark all registers as unused.
- ;;;
- (defun clear-live-set ()
- (dolist (sb (backend-sb-list *backend*))
- (when (eq (sb-kind sb) :finite)
- (fill (finite-sb-live-tns sb) nil))))
-
-
- ;;; CHECK-BLOCK-INIT -- Internal
- ;;;
- ;;; Set up the FINITE-SB-LIVE-TNS to represent the TNs live at a particular
- ;;; point. We mark the TNs, but record no instruction, since we don't know
- ;;; where the read is.
- ;;;
- (defun check-block-init (block live)
- (clear-live-set)
- (do-live-tns (tn live block)
- (let* ((sc (tn-sc tn))
- (sb (sc-sb sc)))
- (when (eq (sb-kind sb) :finite)
- (loop for offset from (tn-offset tn)
- repeat (sc-element-size sc) do
- (setf (svref (finite-sb-live-tns sb) offset)
- (cons tn nil))))))
- (undefined-value))
-
-
- ;;; NOTE-MORE-REFS -- Internal
- ;;;
- ;;; Do NOTE-TN-REF on the more operand to a VOP. Costs are the fixed
- ;;; operand costs (to skip them.) Ops is the full arg/result list.
- ;;;
- (defun note-more-refs (costs ops write-p inst)
- (do ((cost costs (cdr cost))
- (op ops (tn-ref-across op)))
- ((null cost)
- (do ((op op (tn-ref-across op)))
- ((null op))
- (note-tn-ref (tn-ref-tn op) write-p inst))))
- (undefined-value))
-
-
- ;;; FIND-BRANCH-TARGETS -- Internal
- ;;;
- ;;; Return a bit-vector with 1 elements for the offsets of all labels that
- ;;; have an intra-block jump to them. Labels with no VOP are block start
- ;;; labels.
- ;;;
- (defun find-branch-targets (elsewhere)
- (let* ((last (label-%position elsewhere))
- (res (make-array (1+ last) :element-type 'bit :initial-element 0)))
- (do ((node (node-prev elsewhere) (node-prev node)))
- ((null node))
- (when (and (instruction-p node)
- (inst-class-p node relative-branch))
- (do-constants (lab node)
- (when (label-p lab)
- (let ((lab-vop (node-vop lab)))
- (when (and lab-vop
- (eq (ir2-block-block (vop-block lab-vop))
- (ir2-block-block (vop-block (node-vop node)))))
- (let ((pos (label-%position lab)))
- (when (<= pos last)
- (setf (sbit res pos) 1)))))))))
- res))
-
-
- ;;; Call VOPs that don't happen to have the MOVE-ARGUMENTS attribute.
- ;;;
- (defparameter stray-call-vops '(c:call-variable c:call-out))
-
- ;;; SEGMENT-CHECK-REGISTERS -- Interface
- ;;;
- ;;; Check the validity of register allocation in a segment. Elsewhere is
- ;;; the (now inserted) elsewhere segment, which we use to determine the start
- ;;; of elsewhere code (so that we can ignore it.) We detect most (but not all)
- ;;; allocation errors. Code for each Ir2-block must be contiguous (so this
- ;;; must be called before assembly optimization.) We go back to the IR2 to
- ;;; find the live TNs at block ends and call sites.
- ;;;
- ;;; We clear the live set at all labels that are the target of intra-block
- ;;; jumps, since there might be some weird control flow going on that could
- ;;; cause spurious errors.
- ;;;
- (defun segment-check-registers (*check-segment* elsewhere)
- (let ((*check-end* nil)
- (targets (find-branch-targets elsewhere))
- (call-vop nil)
- (state :normal)
- (block nil))
- (declare (type (member :normal :call :assembly-call) state)
- (inline member))
- (do ((node (node-prev elsewhere) (node-prev node)))
- ((null node))
- (typecase node
- (instruction
- (let* ((vop (node-vop node))
- (info (vop-info vop)))
- (unless (eq call-vop vop)
- (ecase state
- (:call
- (note-more-refs (vop-info-arg-costs (vop-info call-vop))
- (vop-args call-vop)
- nil (node-next node)))
- ((:assembly-call :normal)))
- (setq state :normal))
-
- (when (eq state :normal)
- (let ((vblock (vop-block vop)))
- (unless (eq vblock block)
- (setq block vblock)
- (setq *check-end* (node-next node))
- (check-block-init block (ir2-block-live-in block))))
-
- (cond
- ((or (vop-info-move-args info)
- (member (vop-info-name info) stray-call-vops
- :test #'eq))
- (setq state :call call-vop vop)
- (note-more-refs (vop-info-result-costs info)
- (vop-results vop) t node))
- ((inst-class-p node assembly-call)
- (setq state :assembly-call call-vop vop)
- (note-more-refs nil (vop-temps vop) t node))))
-
- (do-results (res node)
- (note-tn-ref res t node))
- (do-arguments (arg node)
- (note-tn-ref arg nil node))))
- (label
- (unless (zerop (sbit targets (label-%position node)))
- (clear-live-set))))))
-
- (undefined-value))
-